﻿using System;
using System.Collections.Generic;
using System.Xml.Linq;

namespace FluentlyXml.Core.Deserialization.BuiltInDeserializers
{
	internal class CustomDeserializer : ITypeDeserializer
	{
		private readonly Type _appliesToType;
		private readonly PropertyDeserializerDictionary _propertyDeserializers;
		private readonly List<Action<object>> _callbacks = new List<Action<object>>();
		private Func<object, object> _identitySelector;
		private Func<object> _factory;

		public CustomDeserializer(Type appliesToType)
		{
			_appliesToType = appliesToType;
			_propertyDeserializers = new PropertyDeserializerDictionary();
			//TODO: Allow for DSL to specify how to create new instances. 
			_factory = () => Activator.CreateInstance(_appliesToType);
		}

		public bool CanDeserialize(Type type)
		{
			return _appliesToType == type;
		}

		public object Deserialize(Type target, XElement element, IDeserializationContext context)
		{
			var result = _factory();

			foreach (var prop in _appliesToType.GetPropertiesForSerialization())
			{
				try
				{
					var deserializer = _propertyDeserializers[prop];

					var propertyElement = element.Element(prop.Name);

					if (propertyElement != null)
					{
						var value = deserializer(context, propertyElement, prop);

						prop.SetValue(result, value, null);						
					}
				}
				catch (Exception ex)
				{
					throw new InvalidOperationException("Error deserializing property '" + _appliesToType.FullName + "." + prop.Name + ".", ex);
				}
			}

			_callbacks.ForEach(c => c(result));

			return result;
		}

		public object GetIdentityFor(object obj)
		{
			return _identitySelector(obj);
		}

		public void AddPropertyDeserializer(string propertyName, PropertyDeserializer deserializer)
		{
			_propertyDeserializers.Add(propertyName, deserializer);
		}

		public void SetIdentityFunction(Func<object,object> identitySelector)
		{
			_identitySelector = identitySelector;
		}

		public void AddPostDeserializationCallback(Action<object> callback)
		{
			_callbacks.Add(callback);
		}
	}
}